package com.tivyso.testing.mock.generator;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;


public class GenericClass {
	
	private static class GenericInfo{
		private Class<?> clazz;
		private Type actualTypeArgument;
		public Class<?> getClazz() {
			return clazz;
		}
		public void setClazz(Class<?> clazz) {
			this.clazz = clazz;
		}
		public Type getActualTypeArgument() {
			return actualTypeArgument;
		}
		public void setActualTypeArgument(Type actualTypeArgument) {
			this.actualTypeArgument = actualTypeArgument;
		}
	}
	
	private static class GenericClassInfo{
		private Map<String, GenericInfo> generics = new HashMap<String, GenericInfo>();
		
		public Map<String, GenericInfo> getGenerics() {
			return generics;
		}
	}
	
	public static class ResultInfo{
		private Class<?> clazz;
		private Type type;
		public Class<?> getClazz() {
			return clazz;
		}
		public void setClazz(Class<?> clazz) {
			this.clazz = clazz;
		}
		public Type getType() {
			return type;
		}
		public void setType(Type type) {
			this.type = type;
		}
	}
	
	private Map<Class<?>, GenericClassInfo> genericRegister;  
	
	public GenericClass(Class<?> clazz){
		genericRegister = new HashMap<Class<?>, GenericClassInfo>();
		loadInterface(clazz);		
	}
	
	private void loadInterface(Class<?> cc){
		if (cc == null){
			return;
		}
		for (Type t: cc.getGenericInterfaces()){
			Class<?> rawClass = null;
			if (ParameterizedType.class.isInstance(t)){
				ParameterizedType pt = ParameterizedType.class.cast(t);
				rawClass = (Class<?>)pt.getRawType();
				GenericClassInfo genericClassInfo = new GenericClassInfo();
				genericRegister.put(rawClass, genericClassInfo);
				for ( int i=0; i<rawClass.getTypeParameters().length; i++){
					GenericInfo genericInfo = new GenericInfo();
					genericInfo.setClazz(cc);
					genericInfo.setActualTypeArgument(pt.getActualTypeArguments()[i]);
					genericClassInfo.getGenerics().put(rawClass.getTypeParameters()[i].getName(), genericInfo);				
				}
			}else{
				rawClass = Class.class.cast(t);
			}
			loadInterface(rawClass);
		}
	}
	
	public ResultInfo resolveType(Class<?> clazz, String name){
		return resolveType(clazz, name, null);
	}
	
	private ResultInfo resolveType(Class<?> clazz, String name, Type callerResult)	{
		Class<?> cc = clazz;
		GenericClassInfo gci = genericRegister.get(cc);
		if (gci != null){
			GenericInfo gi = gci.getGenerics().get(name);
			return resolveType(gi.getClazz(), gi.getActualTypeArgument().toString(), gi.getActualTypeArgument());
		}
		ResultInfo resultInfo = new ResultInfo();
		resultInfo.setClazz(clazz);
		resultInfo.setType(callerResult);
		return resultInfo;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(); 
		for (Entry<Class<?>, GenericClassInfo> e : genericRegister.entrySet()){
			sb.append(e.getKey()).append("\r\n");
			for (Entry<String, GenericInfo> ee : e.getValue().getGenerics().entrySet()){
				sb.append(" > " + ee.getKey() + " : " + ee.getValue().getClazz() + " = " + ee.getValue().getActualTypeArgument()).append("\r\n");
			}
		}
		return sb.toString();	}
}
